package api

import (
	"context"
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"github.com/gin-gonic/gin"
	"go-chain-api/contract"
	"go-chain-api/contract/evmAbi"
	"go-chain-api/global"
	models "go-chain-api/internal/model"
	"go-chain-api/tools"
	"go-chain-api/utils"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
	"log"
	"math/big"
	"net/http"
	"strconv"
	"strings"
	"time"
)

type Robot struct{}

func (r Robot) CreateRobot(c *gin.Context) {
	var robotInfo models.Robot
	err := c.ShouldBindJSON(&robotInfo)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "Parameter missing" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	userInfo := c.MustGet("userInfo").(models.User)
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	var scanConfig models.ScanConfig
	configClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("scan_config")
	err = configClient.FindOne(context.TODO(), bson.M{"chainId": chainId, "chainName": chainName}).Decode(&scanConfig)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "select data failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	clients, err := getClient(scanConfig.RpcUrl)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "Connect rpc failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	//获取代币地址重新判断
	token0, token1, err := contract.GetTokenByTokenAddress(clients, common.HexToAddress(robotInfo.TokenAddress))
	if len(token0) == 0 || len(token1) == 0 {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "token0 or token1 parse failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}

	if robotInfo.BuyToken != token0[0].(common.Address).Hex() && robotInfo.BuyToken != token1[0].(common.Address).Hex() {
		c.JSON(http.StatusBadRequest, gin.H{"error": "buy_token is bad"})
		return
	}

	if robotInfo.SellToken != token0[0].(common.Address).Hex() && robotInfo.SellToken != token1[0].(common.Address).Hex() {
		c.JSON(http.StatusBadRequest, gin.H{"error": "sell_token is bad"})
		return
	}

	if robotInfo.Limit <= 5 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "limit is bad"})
		return
	}

	ValueFloat, err := strconv.ParseFloat(robotInfo.Value, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "value is bad"})
		return

	}
	decimals, err := contract.CallContractMethod(clients, common.HexToAddress(robotInfo.SellToken), evmAbi.UNISWAP_V2_PAIR_ABI, "decimals")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "decimals is bad"})
		return
	}
	valueStr := tools.EtherBigIntToWeiStr(big.NewFloat(ValueFloat), uint(decimals[0].(uint8)))
	//解析私钥
	privateKey, err := crypto.HexToECDSA(robotInfo.PrivateKey)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "privateKey is bad"})
		return
	}

	robotInfo.UpdatedAt = time.Now()
	robotInfo.CreatedAt = time.Now()
	robotInfo.Address = crypto.PubkeyToAddress(privateKey.PublicKey).Hex()
	robotInfo.IsDel = 0
	robotInfo.Value = valueStr
	robotInfo.ChainName = chainName
	robotInfo.ChainId = chainId
	robotInfo.Status = "1"
	robotInfo.UserName = userInfo.Username

	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")
	_, err = robotClient.InsertOne(context.TODO(), robotInfo)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "inserted robot failed:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	response := global.Response{
		Code: http.StatusOK,
		Data: robotInfo,
		Msg:  "success",
	}

	c.JSON(http.StatusOK, response)

}

func (r Robot) GetRobotList(c *gin.Context) {
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	pageSize := c.MustGet("pageSize").(uint)
	pageIndex := c.MustGet("pageIndex").(uint)
	findOptions := options.Find()
	findOptions.SetSkip(int64(pageIndex))
	findOptions.SetLimit(int64(pageSize))
	userInfo := c.MustGet("userInfo").(models.User)
	filter := bson.D{{"chainId", chainId}, {"chainName", chainName}, {"userName", userInfo.Username}, {"isDel", 0}}
	var robotList []*models.Robot
	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")
	result, err := robotClient.Find(context.TODO(), filter, findOptions)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "failed to get data:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	err = result.All(context.Background(), &robotList)
	if err != nil {
		response := global.Response{
			Code: http.StatusBadRequest,
			Data: nil,
			Msg:  "failed to all data:" + err.Error(),
		}
		c.JSON(http.StatusOK, response)
		return
	}
	response := global.Response{
		Code: http.StatusOK,
		Data: robotList,
		Msg:  "success",
	}
	c.JSON(http.StatusOK, response)
}

func (r Robot) GetTokenByAddress(c *gin.Context) {
	language := c.MustGet("language").(string)
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	var config models.ScanConfig
	configClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("scan_config")
	err := configClient.FindOne(context.TODO(), bson.D{{"chainId", chainId}, {"chainName", chainName}}).Decode(&config)
	if err != nil {
		response := utils.GetResponse(err, utils.MongodbNoData, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	chainClient, err := getClient(config.RpcUrl)
	if err != nil {
		response := utils.GetResponse(nil, utils.ServerError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	tokenAddress := c.Query("tokenAddress")
	if tokenAddress == "" {
		response := utils.GetResponse(err, utils.ParamBindError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	token0, token1, err := contract.GetTokenByTokenAddress(chainClient, common.HexToAddress(tokenAddress))
	if err != nil {
		response := utils.GetResponse(err, utils.ServerError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	token0Sym, err := contract.CallContractMethod(chainClient, token0[0].(common.Address), evmAbi.UNISWAP_V2_PAIR_ABI, "symbol")
	if err != nil {
		response := utils.GetResponse(err, utils.ServerError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	token1Sym, err := contract.CallContractMethod(chainClient, token1[0].(common.Address), evmAbi.UNISWAP_V2_PAIR_ABI, "symbol")
	if err != nil {
		response := utils.GetResponse(err, utils.ServerError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	result := make(map[string]string)
	result["token0"] = token0[0].(common.Address).Hex()
	result["token0sym"] = token0Sym[0].(string)
	result["token1"] = token1[0].(common.Address).Hex()
	result["token1sym"] = token1Sym[0].(string)
	response := utils.GetResponse(nil, utils.Success, result, language)
	c.JSON(http.StatusOK, response)
}

func (r Robot) GetRobotDetail(c *gin.Context) {
	language := c.MustGet("language").(string)
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	robotId := c.Query("robotId")
	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")

	if robotId == "" {
		response := utils.GetResponse(nil, utils.ParamBindError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	objId, err := primitive.ObjectIDFromHex(robotId)
	if err != nil {
		response := utils.GetResponse(err, utils.ParseDataError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	filter := bson.D{{"chainId", chainId}, {"chainName", chainName}, {"_id", objId}}

	var robotInfo models.Robot
	err = robotClient.FindOne(context.TODO(), filter).Decode(&robotInfo)
	if err != nil {
		response := utils.GetResponse(err, utils.MongodbNoData, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	response := utils.GetResponse(nil, utils.Success, robotInfo, language)
	c.JSON(http.StatusOK, response)
}

func (r Robot) DelRobot(c *gin.Context) {
	language := c.MustGet("language").(string)
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	robotId := c.Query("robotId")
	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")
	objId, err := primitive.ObjectIDFromHex(robotId)
	if err != nil {
		response := utils.GetResponse(err, utils.ParseDataError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	filter := bson.D{{"chainId", chainId}, {"chainName", chainName}, {"_id", objId}}
	_, err = robotClient.UpdateOne(context.TODO(), filter, bson.D{{"$set", bson.D{{"isDel", 1}}}})
	if err != nil {
		response := utils.GetResponse(err, utils.DeleteError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	response := utils.GetResponse(nil, utils.Success, nil, language)
	c.JSON(http.StatusOK, response)

}
func (r Robot) RenameRobot(c *gin.Context) {
	language := c.MustGet("language").(string)
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	var robotInfo models.Robot
	err := c.ShouldBindJSON(&robotInfo)
	if err != nil {
		response := utils.GetResponse(err, utils.ParseDataError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")
	objId, err := primitive.ObjectIDFromHex(robotInfo.Id)
	if err != nil {
		response := utils.GetResponse(err, utils.ParseDataError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	filter := bson.D{{"chainId", chainId}, {"chainName", chainName}, {"_id", objId}}
	_, err = robotClient.UpdateOne(context.TODO(), filter, bson.D{{"$set", bson.D{{"robotName", robotInfo.RobotName}}}})
	if err != nil {
		response := utils.GetResponse(err, utils.UpdateError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	response := utils.GetResponse(nil, utils.Success, nil, language)
	c.JSON(http.StatusOK, response)
}

func (r Robot) EditStatusRobot(c *gin.Context) {
	language := c.MustGet("language").(string)
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	robotId := c.Query("robotId")
	status := c.Query("status")
	robotType := c.Query("type")
	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")
	if robotType == "single" {
		objId, err := primitive.ObjectIDFromHex(robotId)
		if err != nil {
			response := utils.GetResponse(err, utils.ParseDataError, nil, language)
			c.JSON(http.StatusOK, response)
			return
		}
		filter := bson.D{{"chainId", chainId}, {"chainName", chainName}, {"_id", objId}}
		_, err = robotClient.UpdateOne(context.TODO(), filter, bson.D{{"$set", bson.D{{"status", status}}}})
		if err != nil {
			response := utils.GetResponse(err, utils.UpdateError, nil, language)
			c.JSON(http.StatusOK, response)
			return
		}
	} else {
		filter := bson.D{{"chainId", chainId}, {"chainName", chainName}}
		_, err := robotClient.UpdateMany(context.TODO(), filter, bson.D{{"$set", bson.D{{"status", status}}}})
		if err != nil {
			response := utils.GetResponse(err, utils.UpdateError, nil, language)
			c.JSON(http.StatusOK, response)
			return
		}

	}

	response := utils.GetResponse(nil, utils.Success, nil, language)
	c.JSON(http.StatusOK, response)

}

func (r Robot) GetRobotBalance(c *gin.Context) {
	language := c.MustGet("language").(string)
	chainName := c.MustGet("chainName").(string)
	chainId := c.MustGet("chainId").(uint)
	robotId := c.Query("robotId")
	mainDecimals := c.Query("mainDecimals")
	mainName := c.Query("mainName")
	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")
	objId, err := primitive.ObjectIDFromHex(robotId)
	if err != nil {
		response := utils.GetResponse(err, utils.ParseDataError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	filter := bson.D{{"chainId", chainId}, {"chainName", chainName}, {"_id", objId}}
	var robotInfo models.Robot
	err = robotClient.FindOne(context.TODO(), filter).Decode(&robotInfo)
	if err != nil {
		response := utils.GetResponse(err, utils.MongodbNoData, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	var config models.ScanConfig
	configClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("scan_config")
	err = configClient.FindOne(context.TODO(), bson.D{{"chainId", chainId}, {"chainName", chainName}}).Decode(&config)
	if err != nil {
		response := utils.GetResponse(err, utils.MongodbNoData, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	clients, err := getClient(config.RpcUrl)
	if err != nil {
		response := utils.GetResponse(nil, utils.ServerError, nil, language)
		c.JSON(http.StatusOK, response)
		return
	}
	number, err := clients.BalanceAt(context.Background(), common.HexToAddress(robotInfo.Address), nil)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to get the balance"})
		return
	}
	// 代币合约的ABI和地址
	tokenABI := evmAbi.ERC20_ABI
	parsedABI, err := abi.JSON(strings.NewReader(tokenABI))
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to get the abi"})
		return
	}
	// 用户地址
	userAddress := common.HexToAddress(robotInfo.Address) // 替换为用户地址
	BuyToken := common.HexToAddress(robotInfo.BuyToken)   // 替换为用户地址
	SellToken := common.HexToAddress(robotInfo.SellToken) // 替换为用户地址
	// 调用合约方法
	data, err := parsedABI.Pack("balanceOf", userAddress)
	if err != nil {
		log.Fatal(err)
	}
	BuyResult, err := clients.CallContract(context.Background(), ethereum.CallMsg{
		From:     userAddress,
		To:       &BuyToken,
		Gas:      100000,
		GasPrice: big.NewInt(20000000000),
		Value:    big.NewInt(0),
		Data:     data,
	}, nil)

	if err != nil {
		log.Fatal(err)
	}
	SellResult, err := clients.CallContract(context.Background(), ethereum.CallMsg{
		From:     userAddress,
		To:       &SellToken,
		Gas:      100000,
		GasPrice: big.NewInt(20000000000),
		Value:    big.NewInt(0),
		Data:     data,
	}, nil)

	if err != nil {
		log.Fatal(err)
	}

	// 解析结果
	BuyBalance, err := parsedABI.Unpack("balanceOf", BuyResult)
	if err != nil {
		log.Fatal(err)
	}
	// 解析结果
	SellBalance, err := parsedABI.Unpack("balanceOf", SellResult)
	if err != nil {
		log.Fatal(err)
	}
	//调整精度
	BuyDecimals, err := contract.CallContractMethod(clients, common.HexToAddress(robotInfo.BuyToken), evmAbi.UNISWAP_V2_PAIR_ABI, "decimals")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "decimals is bad"})
		return
	}
	BuyBalanceStr := "0"
	BuyValue, isOk := tools.WeiStrToEtherBigInt(BuyBalance[0].(*big.Int).String(), uint(BuyDecimals[0].(uint8)))
	if isOk {
		BuyBalanceStr = BuyValue.Text('f', 6)
	}
	//调整精度
	BuySymbol, err := contract.CallContractMethod(clients, common.HexToAddress(robotInfo.BuyToken), evmAbi.UNISWAP_V2_PAIR_ABI, "symbol")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "symbol is bad"})
		return
	}
	//调整精度
	SellDecimals, err := contract.CallContractMethod(clients, common.HexToAddress(robotInfo.SellToken), evmAbi.UNISWAP_V2_PAIR_ABI, "decimals")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "decimals is bad"})
		return
	}
	SellBalanceStr := "0"
	SellValue, isOk := tools.WeiStrToEtherBigInt(SellBalance[0].(*big.Int).String(), uint(SellDecimals[0].(uint8)))
	if isOk {
		SellBalanceStr = SellValue.Text('f', 6)
	}
	//调整精度
	SellSymbol, err := contract.CallContractMethod(clients, common.HexToAddress(robotInfo.SellToken), evmAbi.UNISWAP_V2_PAIR_ABI, "symbol")
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "symbol is bad"})
		return
	}
	mainBalanceStr := "0"
	MainDecimalsUint, err := strconv.ParseUint(mainDecimals, 10, 64)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "MainDecimals is bad"})
		return
	}
	mainValue, isOk := tools.WeiStrToEtherBigInt(number.String(), uint(MainDecimalsUint))
	if isOk {
		mainBalanceStr = mainValue.Text('f', 6)
	}
	result := make(map[string]string)
	result["buy_symbol"] = BuySymbol[0].(string)
	result["buy_balance"] = BuyBalanceStr
	result["sell_symbol"] = SellSymbol[0].(string)
	result["sell_balance"] = SellBalanceStr
	result["main_symbol"] = mainName
	result["main_balance"] = mainBalanceStr

	response := utils.GetResponse(nil, utils.Success, result, language)
	c.JSON(http.StatusOK, response)

}

func getClient(rpcUrl string) (*ethclient.Client, error) {
	return ethclient.Dial(rpcUrl)
}

var i = 0

// RobotServer 机器人刷单
func RobotServer() {
	//创建自增触发器
	//RobotDoIt(manager, db, i)
	//return
	// 创建一个定时器，每隔1秒触发一次
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C: // 等待定时器触发
			go RobotDoIt()
			// 在这里执行你想要的操作
		}
	}
}

func RobotDoIt() {
	//触发器自增
	i++
	//重置触发器  防止溢出
	if i > 86400000 {
		i = 0
	}
	//读取全部机器人
	robotList, err := getRobotList()
	if err != nil {
		log.Printf("获取机器人列表错误： %s ", err.Error())
		return
	}
	//循环所有机器人  判断那个在当前时间需要执行
	for _, robot := range robotList {
		//判断当前是否触发
		if i%int(robot.Limit) == 0 {
			go toSwap(robot)
		}
	}
}

func toSwap(robot *models.Robot) {
	//获取链信息
	var chainConfig models.ScanConfig
	configClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("scan_config")
	err := configClient.FindOne(context.TODO(), bson.M{"chainId": robot.ChainId, "chainName": robot.ChainName}).Decode(&chainConfig)
	if err != nil {
		log.Printf("获取连信息出错:%s", err)
		return
	}
	//获取client
	clientHandel, err := getClient(chainConfig.RpcUrl)
	if err != nil {
		log.Printf("获取机器人gethclient获取失败： %s ", err.Error())
		return
	}
	//私钥转换
	privateKey, err := crypto.HexToECDSA(robot.PrivateKey)
	if err != nil {
		log.Printf("获取机器人privateKey转换失败： %s ", err.Error())
		return
	}
	//合约地址
	tokenAddress := common.HexToAddress(robot.RouterAddress)
	//交换集合
	path := []common.Address{common.HexToAddress(robot.SellToken), common.HexToAddress(robot.BuyToken)}
	//交易金额
	amountIn := new(big.Int)
	amountIn.SetString(robot.Value, 10)
	amountOutMin := big.NewInt(0)
	to := common.HexToAddress(robot.Address)                     // 接收者地址
	deadline := big.NewInt(time.Now().Add(time.Hour * 1).Unix()) // 截止时间
	tx, err := contract.SwapExactTokensForTokensFee(clientHandel, privateKey, tokenAddress, to, path, amountIn, amountOutMin, deadline)
	if err != nil {
		log.Printf("机器人发送swap交易失败： %s ", err.Error())
		return
	}
	//存储交易结果
	var robotLogs models.RobotLog
	robotLogs.RobotId = robot.Id
	decimals, err := contract.CallContractMethod(clientHandel, common.HexToAddress(robot.SellToken), evmAbi.UNISWAP_V2_PAIR_ABI, "decimals")
	if err != nil {
		log.Printf("获取机器人交易精度失败： %s ", err.Error())
		return
	}
	robotLogs.Decimals = uint(decimals[0].(uint8))
	robotLogs.ChainId = robot.ChainId
	robotLogs.Address = robot.Address
	robotLogs.Value = robot.Value
	robotLogs.TokenAddress = robot.TokenAddress
	robotLogs.BuyToken = robot.BuyToken
	robotLogs.SellToken = robot.SellToken
	Symbol, err := contract.CallContractMethod(clientHandel, common.HexToAddress(robot.BuyToken), evmAbi.UNISWAP_V2_PAIR_ABI, "symbol")
	if err != nil {
		log.Printf("获取机器人交易精度失败： %s ", err.Error())
		return
	}
	robotLogs.Symbol = Symbol[0].(string)
	robotLogs.TxHash = tx.Hash().Hex()
	var robotLogClient = global.DBEngine.Database(global.DbConfig.DbName).Collection("robot_log")
	_, err = robotLogClient.InsertOne(context.TODO(), robotLogs)
	if err != nil {
		log.Printf("保存机器人交易记录失败： %s ", err.Error())
		return
	}
	return
}

// 获取活动的机器人
func getRobotList() ([]*models.Robot, error) {
	var robotList []*models.Robot
	filter := bson.D{{"isDel", 0}, {"status", "1"}}
	robotClient := global.DBEngine.Database(global.DbConfig.DbName).Collection("robot")
	result, err := robotClient.Find(context.TODO(), filter)
	if err != nil {
		return nil, err
	}
	err = result.All(context.TODO(), &robotList)
	if err != nil {
		return nil, err
	}

	return robotList, nil
}
